#pragma once
#include <array>
#include <fmt/format.h>
#include <initializer_list>

struct GLSLGenerator {
public:
    inline GLSLGenerator() {
        code.reserve(4096);
        header();
    }
    template <typename... Type> inline void append(const char *format, Type... value) {
        fmt::format_to(std::back_inserter(code), format, value...);
    }

public:
    struct GLSLVariable {
        const char *typeName;
        const char *name;
    };
    inline GLSLGenerator &declVar(GLSLVariable var) {
        append("{} {};", var.typeName, var.name);
        return *this;
    }

public:
    inline GLSLGenerator &attrIn(int location, GLSLVariable var) {
        append("layout(location={})in {} {};", location, var.typeName, var.name);
        return *this;
    };
    inline GLSLGenerator &attrIn(std::initializer_list<GLSLVariable> vars) {
        int index = 0;
        for (auto var : vars) {
            append("layout(location={})in {} {};", index++, var.typeName, var.name);
        }
        return *this;
    }
    inline GLSLGenerator &attrOut(int location, GLSLVariable var) {
        append("layout(location={})out {} {};", location, var.typeName, var.name);
        return *this;
    };
    inline GLSLGenerator &attrOut(std::initializer_list<GLSLVariable> vars) {
        int index = 0;
        for (auto var : vars) {
            append("layout(location={})out {} {};", index++, var.typeName, var.name);
        }
        return *this;
    }
    template <typename Str>
    inline GLSLGenerator &structType(const Str &typeName, std::initializer_list<GLSLVariable> vars) {
        append("struct {}{{", typeName);
        for (auto var : vars) {
            declVar(var);
        }
        append("}};");
    }
    template <typename Str>
    inline GLSLGenerator &uniformBlock(const Str &blockName, int binding, std::initializer_list<GLSLVariable> vars) {
        append("layout(binding={},std430)uniform {}{{", binding, blockName);
        for (auto var : vars) {
            declVar(var);
        }
        append("}};");
    }
    template <typename Str> inline GLSLGenerator &main(const Str &code_) { append("void main(){{{}}}", code_); }

public:
    std::string code;

private:
    static constexpr std::array<const char *, 4> extensions = {
        "GL_ARB_uniform_buffer_object",
        "GL_ARB_explicit_attrib_location",
        "GL_ARB_explicit_uniform_location",
        "GL_NV_uniform_buffer_std430_layout",
    };
    static constexpr auto version = "450 core";

    inline void header() {
        append("#version {}\n", version);
        for (auto ext : extensions) {
            append("#extension {}:enable\n", ext);
        }
    }
};